package mm.chenchen.common.utils;


import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class BaseRepository<T> {
    @PersistenceContext
    protected EntityManager entityManager;
    @Autowired
    protected JdbcTemplate jdbcTemplate;
    @Autowired
    protected NamedParameterJdbcTemplate namedJdbcTemplate;
    private static Logger LOG = LoggerFactory.getLogger(BaseRepository.class);
    @Value("${spring.datasource.druid.driverClassName}")
    private String driverClassName;
    @Value("${spring.jpa.properties.hibernate.show_sql}")
    private boolean showSql;

    public BaseRepository() {
    }

    protected Session openSession() {
        EntityManagerFactory factory = this.entityManager.getEntityManagerFactory();
        if (factory.unwrap(SessionFactory.class) == null) {
            throw new NullPointerException("factory is not a hibernate factory");
        } else {
            SessionFactory hibernateFactory = (SessionFactory)factory.unwrap(SessionFactory.class);
            Session session = hibernateFactory.openSession();
            return session;
        }
    }

    protected String getReplaceString() {
        return this.driverClassName.indexOf("mysql") >= 0 ? null : " escape '/'";
    }

    protected String getLikeParam(String param, StringBuffer paramHql) {
        return "%" + this.replaceIllegal(param, paramHql) + "%";
    }

    protected String getStartWithParam(String param, StringBuffer paramHql) {
        return this.replaceIllegal(param, paramHql) + "%";
    }

    private String replaceIllegal(String param, StringBuffer paramHql) {
        if (!StringUtils.isEmpty(param) && (param.indexOf("%") >= 0 || param.indexOf("_") >= 0)) {
            if (this.driverClassName.indexOf("mysql") >= 0) {
                return param.replace("%", "\\%").replace("_", "\\_");
            } else {
                paramHql.append(" escape '\\'");
                return param.replace("%", "\\%").replace("_", "\\_");
            }
        } else {
            return param;
        }
    }

    public PageData<T> getPagingData(String countHql, String queryHql, ArrayList<Object> paramValues, PagingParam param) {
        Query query = this.entityManager.createQuery(queryHql);
        Query queryCount = this.entityManager.createQuery(countHql, Long.class);
        if (paramValues != null) {
            for(int i = 0; i < paramValues.size(); ++i) {
                if (paramValues.get(i) != null) {
                    query.setParameter(i, paramValues.get(i));
                    queryCount.setParameter(i, paramValues.get(i));
                }
            }
        }

        Long totalCount = (Long)queryCount.getSingleResult();
        query.setFirstResult(param.getStart());
        query.setMaxResults(param.getLimit());
        PageData<T> pm = new PageData();
        pm.setData(query.getResultList());
        pm.setTotal(totalCount);
        this.entityManager.clear();
        return pm;
    }

    public List<T> getList(String queryHql, ArrayList<Object> paramValues) {
        Query query = this.entityManager.createQuery(queryHql);
        if (paramValues != null) {
            for(int i = 0; i < paramValues.size(); ++i) {
                if (paramValues.get(i) != null) {
                    query.setParameter(i, paramValues.get(i));
                }
            }
        }

        return query.getResultList();
    }

    public List<Object> getQueryList(String queryHql, ArrayList<Object> paramValues) {
        Query query = this.entityManager.createQuery(queryHql);
        if (paramValues != null) {
            for(int i = 0; i < paramValues.size(); ++i) {
                if (paramValues.get(i) != null) {
                    query.setParameter(i, paramValues.get(i));
                }
            }
        }

        return query.getResultList();
    }

    public T getObject(String queryHql, ArrayList<Object> paramValues) {
        Query query = this.entityManager.createQuery(queryHql);
        if (paramValues != null) {
            for(int i = 0; i < paramValues.size(); ++i) {
                if (paramValues.get(i) != null) {
                    query.setParameter(i, paramValues.get(i));
                }
            }
        }

        List<T> datas = query.getResultList();
        return datas != null && datas.size() == 1 ? datas.get(0) : null;
    }

    public int executeUpdate(String sql, final List<Object> params, final List<Object> paramsType) {
        if (this.showSql) {
            LOG.info("\n{} \n args={} argTypes={}  ", new Object[]{sql, params, paramsType});
        }

        PreparedStatementSetter ppss = new PreparedStatementSetter() {
            public void setValues(PreparedStatement pps) throws SQLException {
                if (params != null && params.size() > 0) {
                    for(int i = 0; i < params.size(); ++i) {
                        pps.setObject(i + 1, params.get(i), Integer.valueOf(paramsType.get(i).toString()));
                    }
                }

            }
        };
        return this.executeUpdate(sql, ppss);
    }

    public int executeUpdate(String sql, PreparedStatementSetter ppss) {
        int result;
        if (ppss == null) {
            result = this.jdbcTemplate.update(sql);
        } else {
            result = this.jdbcTemplate.update(sql, ppss);
        }

        return result;
    }

    public PageModel queryBySql(String sql, List<Object> params, List<Object> paramsType, int page, int length, Class mappedClass) {
        PageModel pageModel = new PageModel();
        Object[] args = null;
        int[] argTypes = null;
        if (params != null && params.size() > 0) {
            int paramSize = params.size();
            argTypes = new int[paramSize];
            args = new Object[paramSize];

            for(int i = 0; i < params.size(); ++i) {
                args[i] = params.get(i);
                argTypes[i] = (Integer)paramsType.get(i);
            }
        }

        String pageSql = sql;
        if (this.needPagging((page-1)*length, length)) {
            String countSql = "select count(1) totalCount from (" + sql + ")  tmp";
            StringBuffer sb = new StringBuffer();
            if (this.driverClassName.indexOf("mysql") >= 0) {
                sb.append(" select * from (  ");
                sb.append(sql);
                sb.append(" )as tmp limit ");
                sb.append((page-1)*length);
                sb.append(",");
                sb.append(length);
            } else if (this.driverClassName.indexOf("postgresql") >= 0) {
                sb.append(" select * from (  ");
                sb.append(sql);
                sb.append(" )as tmp limit ");
                sb.append(length);
                sb.append(" offset ");
                sb.append((page-1)*length);
            } else {
                sb.append(" select * from ( select row_.*, rownum rownum_  from (  ");
                sb.append(sql);
                sb.append(" )  row_ where rownum <= ");
                sb.append(length);
                sb.append(" )  where rownum_ >   ");
                sb.append((page-1)*length);
            }

            pageSql = sb.toString();
            SqlRowSet countRs = this.jdbcTemplate.queryForRowSet(countSql, args, argTypes);
            countRs.next();
            int totalCount = countRs.getInt("totalCount");
            pageModel.setTotalNum(totalCount);
        }

        if (this.showSql) {
            LOG.info("\n{} \n args={} argTypes={}  ", new Object[]{pageSql, args, argTypes});
        }

        List dataRs = this.jdbcTemplate.query(pageSql, args, argTypes, new BeanPropertyRowMapper(mappedClass));
        pageModel.setData(dataRs);
        return pageModel;
    }

    public PageModel queryBySql(String sql, List<Object> params, List<Object> paramsType, int start, int length) {
        PageModel pageModel = new PageModel();
        Object[] args = null;
        int[] argTypes = null;
        if (params != null && params.size() > 0) {
            int paramSize = params.size();
            argTypes = new int[paramSize];
            args = new Object[paramSize];

            for(int i = 0; i < params.size(); ++i) {
                args[i] = params.get(i);
                argTypes[i] = (Integer)paramsType.get(i);
            }
        }

        String pageSql = sql;
        if (this.needPagging(start, length)) {
            String countSql = "select count(1) totalCount from (" + sql + ")  tmp";
            StringBuffer sb = new StringBuffer();
            if (this.driverClassName.indexOf("mysql") >= 0) {
                sb.append(" select * from (  ");
                sb.append(sql);
                sb.append(" )as tmp limit ");
                sb.append(start);
                sb.append(",");
                sb.append(length);
            } else if (this.driverClassName.indexOf("postgresql") >= 0) {
                sb.append(" select * from (  ");
                sb.append(sql);
                sb.append(" )as tmp limit ");
                sb.append(length);
                sb.append(" offset ");
                sb.append(start);
            } else {
                sb.append(" select * from ( select row_.*, rownum rownum_  from (  ");
                sb.append(sql);
                sb.append(" )  row_ where rownum <= ");
                sb.append(length);
                sb.append(" )  where rownum_ >   ");
                sb.append(start);
            }

            pageSql = sb.toString();
            if (this.showSql) {
                LOG.info("\n{} \n args={} argTypes={} ", new Object[]{pageSql, args, argTypes});
            }

            SqlRowSet countRs = this.jdbcTemplate.queryForRowSet(countSql, args, argTypes);
            countRs.next();
            int totalCount = countRs.getInt("totalCount");
            pageModel.setTotalNum(totalCount);
        }

        List<Map<String, Object>> dataRs = this.jdbcTemplate.queryForList(pageSql, args, argTypes);
        pageModel.setData(dataRs);
        return pageModel;
    }

    public List queryBySql(String sql, List<Object> params, List<Object> paramsType, Class mappedClass) {
        Object[] args = null;
        int[] argTypes = null;
        if (params != null && params.size() > 0) {
            int paramSize = params.size();
            argTypes = new int[paramSize];
            args = new Object[paramSize];

            for(int i = 0; i < params.size(); ++i) {
                args[i] = params.get(i);
                argTypes[i] = (Integer)paramsType.get(i);
            }
        }

        if (this.showSql) {
            LOG.info("\n{} \n args={} argTypes={} ", new Object[]{sql, args, argTypes});
        }

        List rs = this.jdbcTemplate.query(sql, args, new BeanPropertyRowMapper(mappedClass));
        return rs;
    }

    public SqlRowSet queryBySql(String sql, List<Object> params, List<Object> paramsType) {
        Object[] args = null;
        int[] argTypes = null;
        if (params != null && params.size() > 0) {
            int paramSize = params.size();
            argTypes = new int[paramSize];
            args = new Object[paramSize];

            for(int i = 0; i < params.size(); ++i) {
                args[i] = params.get(i);
                argTypes[i] = (Integer)paramsType.get(i);
            }
        }

        if (this.showSql) {
            LOG.info("\n{} \n args={} argTypes={} ", new Object[]{sql, args, argTypes});
        }

        SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql, args, argTypes);
        return rs;
    }

    private boolean needPagging(int start, int length) {
        if (start < 0) {
            return false;
        } else {
            return length >= 0;
        }
    }
}

